package yolo

import (
	"appengine"
	"appengine/channel"
	"appengine/datastore"
	"appengine/memcache"
	"crypto/rand"
	"encoding/base64"
	"io"
	"net/http"
	"time"
)

func init() {
	// for channel_presence (from=<token>)
	http.HandleFunc("/_ah/channel/connected/", channelConnected)
	http.HandleFunc("/_ah/channel/disconnected/", channelDisconnected)

//	activityNotifiers[ActivityChromiumTreeOpen] = ActivityNotifier(notifyChromiumTreeOpen)
//	activityNotifiers[ActivityChromiumTreeClose] = ActivityNotifier(notifyChromiumTreeClose)
}

// key: clientId
type ChannelClient struct {
	ClientId  string
	User      string // email
	Token     string
	Connected bool
}

const notificationMessageKey = "notification:message"

type NotificationMessage struct {
	Message string
	Time    time.Time
}

func newChannelKey(c appengine.Context, clientId string) *datastore.Key {
	return datastore.NewKey(c, "Channel", clientId, 0, nil)
}

func createChannelToken(c appengine.Context, user string) (token string, err error) {
	buf := make([]byte, 8)
	n, _ := io.ReadFull(rand.Reader, buf)
	clientId := user + "-" + base64.StdEncoding.EncodeToString(buf[:n])
	token, err = channel.Create(c, clientId)
	if err != nil {
		c.Debugf("create channel error: %s", err)
		return "", err
	}
	c.Debugf("clientId:%s token:%s", clientId, token)
	key := newChannelKey(c, clientId)
	_, err = datastore.Put(c, key, &ChannelClient{ClientId: clientId, User: user, Token: token})
	if err != nil {
		c.Warningf("put ChannelClient err:%s", err)
	}
	return token, nil
}

func updateChannel(c appengine.Context, clientId string, updater func(client *ChannelClient)) ChannelClient {
	var client ChannelClient
	key := newChannelKey(c, clientId)
	err := datastore.Get(c, key, &client)
	if err != nil {
		c.Warningf("no channel %s: %s", clientId, err)
		return client
	}
	updater(&client)
	client.ClientId = clientId
	_, err = datastore.Put(c, key, &client)
	if err != nil {
		c.Warningf("put %s failed: %s", clientId, err)
	}
	return client
}

// channel presence handler
func channelConnected(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	from := r.FormValue("from")
	if from == "" {
		return
	}
	client := updateChannel(c, from, func(client *ChannelClient) {
		client.Connected = true
	})
	c.Debugf("connected %s %s", from, client.User)

	var msg NotificationMessage
	_, err := memcache.JSON.Get(c, notificationMessageKey, &msg)
	if err != nil {
		c.Warningf("message err:%s", err)
		return
	}
	c.Debugf("send '%s' to %s", msg.Message, from)
	err = channel.Send(c, from, msg.Message)
	if err != nil {
		c.Warningf("send err:%s", err)
	}
}

func channelDisconnected(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	from := r.FormValue("from")
	if from == "" {
		return
	}
	c.Debugf("disconnected %s", from)
	updateChannel(c, from, func(client *ChannelClient) {
		client.Connected = false
	})
}

func setNotificationGreetingMessage(c appengine.Context, s string, t time.Time) bool {
	var msg NotificationMessage
	_, err := memcache.JSON.Get(c, notificationMessageKey, &msg)
	if err != nil {
		c.Warningf("no msg: %s", err)
	} else if msg.Time.After(t) {
		c.Debugf("old msg: %d > %d", msg.Time, t)
		return false
	}
	msg.Message = s
	msg.Time = t
	item := &memcache.Item{Key: notificationMessageKey, Object: msg}
	err = memcache.JSON.Set(c, item)
	if err != nil {
		c.Warningf("put failed: %s", err)
	}
	return true
}

func notifyMessage(c appengine.Context, s string) {
	q := datastore.NewQuery("Channel").Filter("Connected = ", true)
	var clients []ChannelClient
	_, err := q.GetAll(c, &clients)
	if err != nil {
		c.Warningf("GetAll err:%s", err)
		return
	}
	if len(clients) == 0 {
		return
	}
	for _, client := range clients {
		c.Debugf("send %s to %s", s, client.ClientId)
		err := channel.Send(c, client.ClientId, s)
		if err != nil {
			c.Warningf("send to %s err:%s", client.ClientId, err)
		}
	}
}
